package service;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import model.JoinLeague;
import model.League;
import model.User;

import org.apache.commons.beanutils.BeanUtils;


import exceptions.DataException;
import exceptions.LeagueNotFoundException;
import exceptions.LeagueRestrictionException;
import exceptions.UserNotFoundException;

/**
 * Class that enables operations Create/Read/Update/Delete (CRUD) for leagues.
 * Deleting a league must be confirmed and will remove the seasons,
 * win/loss results (and other data, but not players) associated with the league.
 * Any league attribute may be changed at any time.
 * Results in a league will not affect any data in other leagues.
 * However, league operators may wish to access public data from other leagues for their own internal need
 * @author Andre Melo
 * @author Marcus Leite 
 * @author Sergio Danilo
 *
 */
public class LeagueServiceImpl implements LeagueService {

	/*
	 * The Entity Manager, responsible for the operations with the database.
	 */
	@PersistenceContext
	private EntityManager em;
	
	/*
	 * The instance of UserService.
	 */
	private UserService userService;
	
	@Override
	public void changeLeagueAttribute(String id, String attribute, String value) throws DataException, IllegalAccessException, InvocationTargetException, LeagueRestrictionException {
		if(attribute == null || attribute.equals("")){
			throw new DataException("Must provide an attribute to be changed");
		}
		Query query = em.createQuery("Select l From League l Where l.id = :id");
		query.setParameter("id",Integer.parseInt(id));
		@SuppressWarnings("unchecked")
		List<League> league = query.getResultList();
		if (attribute.equals("name")) {
			verifyAttributeEmpty(attribute, value, "Required data: league name");
			verifyLeagueNameExistence(value);
			BeanUtils.setProperty(league.get(0), attribute, value);
			update(league.get(0));
		} else
		if (attribute.equals("operator")) {
			verifyAttributeEmpty(attribute, value, "Required data: league operator");
			try {
				if (userService.isAnExistentUser(Integer.parseInt(value))){
					BeanUtils.setProperty(league.get(0), attribute, userService.findById(Integer.parseInt(value))); // Need to be user instead of value
					update(league.get(0));
				} else
					throw new DataException("Unknown user");
			} catch (NumberFormatException e) {
				throw new DataException("Unknown user");
			}
		} else {
			throw new DataException("Unknown league attribute");
		}
	}

	@Override
	public Integer createLeague(String name, Integer operatorId) throws DataException, LeagueRestrictionException{
		User user = userService.findById(operatorId);
		verifyLeagueAttributes(name, user);
		League newLeague = new League(name, user);
		return save(newLeague, user.getId());
	}

	@Override
	public League delete(Integer id) {
		League league = findById(id);
		userService.findById(league.getOperator().getId()).setNotOperator();
		em.remove(league);
		return league;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void deleteAll() {
		Query query = em.createQuery("Select l From League l");
		List<League> leagueList = query.getResultList();
		int listSize = leagueList.size();
		for (int i=0;i<listSize; i++){
			em.remove(leagueList.get(i));
		}		
	}

	@Override
	public League findById(Integer id) {
		return em.find(League.class, id);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<String> findLeague(String name) throws LeagueNotFoundException {
		List<String> leagues = new ArrayList<String>();
		Query query = em.createQuery("Select l From League l");
		List<League> listOfLeagues = query.getResultList();
		for (League league : listOfLeagues) { 
			if (league.getName().toLowerCase().equals(name.toLowerCase())){
				leagues.add(league.getName());
			}
			else if(league.getName().toLowerCase().matches(name.toLowerCase())){
				leagues.add(league.getName());
			}
		}
		if (leagues.isEmpty())
			throw new LeagueNotFoundException("Could not find league "+name);
		Collections.sort(leagues);
		return leagues;
	}

	@SuppressWarnings("unchecked")
	@Override
	public String getLeagueAttribute(String id, String attribute) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Query query = em.createQuery("Select l From League l Where l.id = :id");
		query.setParameter("id", Integer.parseInt(id));
		List<League> league = query.getResultList();
		return BeanUtils.getProperty(league.get(0), attribute);
	}

	@Override
	public List<User> getLeagueMembers(Integer leagueId) throws LeagueNotFoundException {
		verifyLeagueExistence(leagueId);
		League league = findById(leagueId);
		List<JoinLeague> joinLeagues = league.getJoinLeagues();
		List<User> users = new ArrayList<User>();
		for (JoinLeague joinLeague: joinLeagues) {
			users.add(joinLeague.getUser());
		}
		return users;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<League> getPlayerLeagues(Integer userId) throws UserNotFoundException, LeagueNotFoundException {
		userService.isAnExistentUser(userId);
		Query query = em.createQuery("Select l From League l");
		List<League> leagues = query.getResultList();
		List<League> userLeagues = new ArrayList<League>();
		for (League league: leagues) {
			if (isLeagueMember(userId, league.getId()))
				userLeagues.add(league);
		}
		return userLeagues;
	}
	
	@Override
	public String getUserLeagueAttribute(Integer userId, Integer leagueId, String attribute) throws UserNotFoundException, LeagueNotFoundException, DataException, LeagueRestrictionException {
		verifyUserExistence(userId);
		verifyLeagueExistence(leagueId);
		if (!isLeagueMember(userId, leagueId)){
			throw new UserNotFoundException("User is not a league member");
		}
		League league = findById(leagueId);
		List<JoinLeague> joinLeagues = league.getJoinLeagues();
		if (attribute.equals("joinDate")) {
			for (JoinLeague joinLeague: joinLeagues) {
				if (joinLeague.getUser().getId().equals(userId))
					return joinLeague.getJoinDate().toString();
			}
			return "";
		}else if (attribute.equals("currentHandicap")) {
			for (JoinLeague joinLeague: joinLeagues) {
				if (joinLeague.getUser().getId().equals(userId))
					return joinLeague.getHandicap().toString();
			}
			return "";
		}else 
			throw new DataException("Unknown user attribute");
	}
	
	@Override
	public UserService getUserService() {
		return userService;
	}

	@Override
	public boolean isLeagueMember(Integer userId, Integer leagueId) throws UserNotFoundException, LeagueNotFoundException {
		if (!userService.isAnExistentUser(userId)){
			throw new UserNotFoundException("Unknown user");
		}
		verifyLeagueExistence(leagueId);
		League league = findById(leagueId);
		List<JoinLeague> joinLeagues = league.getJoinLeagues();
		for (JoinLeague joinLeague: joinLeagues) {
			if (joinLeague.getUser().getId().equals(userId))
				return true;
		}
		return false;
	}	

	@Override
	public void joinLeague(Integer userId, Integer leagueId, Integer initialHandicap) throws DataException, LeagueNotFoundException, LeagueRestrictionException, UserNotFoundException {
		verifyHandicap(initialHandicap);
		verifyLeagueExistence(leagueId);
		verifyUserExistence(userId);
		verifyIfUserAlreadyExistsInLeague(userId, leagueId);
		League league = findById(leagueId);
		User user = userService.findById(userId);
		JoinLeague jl = new JoinLeague();
		jl.setUser(user);
		jl.setDate(new Date());
		jl.setHandicap(initialHandicap);
		List<JoinLeague> JoinLeagues = league.getJoinLeagues();
		JoinLeagues.add(jl);	
		league.setJoinLeagues(JoinLeagues);
		update(league);
	}

	@Override
	public void leaveLeague(Integer userId, Integer leagueId) throws LeagueRestrictionException, UserNotFoundException, LeagueNotFoundException {
		verifyUserExistence(userId);
		verifyLeagueExistence(leagueId);
		if (!isLeagueMember(userId, leagueId)){
			throw new UserNotFoundException("User is not a league member");
		}
		League league = findById(leagueId);
		if (league.getOperator().getId().compareTo(userId) == 0){
			throw new LeagueRestrictionException("Operator cannot leave league");
		}
		List<JoinLeague> joinLeagues = league.getJoinLeagues();
		List<JoinLeague> jlResult = new ArrayList<JoinLeague>();
		for (JoinLeague joinLeague: joinLeagues) {
			if (!joinLeague.getUser().getId().equals(userId)){
					jlResult.add(joinLeague);				
			}
		}
		league.setJoinLeagues(jlResult);
		update(league);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<League> listAll() {
		Query query = em.createQuery("Select l From League l Order By l.name");
		return query.getResultList();
	}
	
	@Override
	public Integer save(League league, Integer operatorId) {
		userService.findById(operatorId).setOperator();
		User user = userService.findById(operatorId);
		JoinLeague jl = new JoinLeague();
		jl.setUser(user);
		jl.setDate(new Date());
		jl.setHandicap(user.getHandicap());
		List<JoinLeague> JoinLeagues = new ArrayList<JoinLeague>();
		JoinLeagues.add(jl);	
		league.setJoinLeagues(JoinLeagues);
		league.setOperator(user);
		em.persist(league);
		return league.getId();
	}
	
	@Override
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	
	@Override
	public void update(League league) {
		em.merge(league);
	}
	
	/*
	 * Verify if a attribute is empty
	 * @param attribute The checked attribute
	 * @param value The value of the attribute 
	 * @param message The error message
	 * @throws DataException
	 */
	private void verifyAttributeEmpty(String attribute, String value, String message) throws DataException {
		if (value == null || value.equals("")) 
			throw new DataException(message);
	}

	/*
	 * Verify the initial handicap
	 * @param initialHandicap The initial handicap
	 * @throws DataException
	 */
	private void verifyHandicap(Integer initialHandicap) throws DataException {
		if (initialHandicap == null) 
			throw new DataException("Must provide initial player handicap");
		if (initialHandicap < 0)
			throw new DataException("Handicap cant be negative");
	}

	/*
	 * Verify if an user already exists in the League
	 * @param userId The ID of the user
	 * @param leagueId The ID of the league
	 * @throws LeagueRestrictionException
	 * @throws UserNotFoundException
	 * @throws LeagueNotFoundException
	 */
	private void verifyIfUserAlreadyExistsInLeague(Integer userId, Integer leagueId) throws LeagueRestrictionException, UserNotFoundException, LeagueNotFoundException  {
		if (isLeagueMember(userId, leagueId))
			throw new LeagueRestrictionException("User is already a league member");
	}

	/*
	 * Verify the league's attributes
	 * @param name The name of the attribute
	 * @param operator The league's operator
	 * @throws DataException
	 * @throws LeagueRestrictionException
	 */
	private void verifyLeagueAttributes(String name, User operator) throws DataException, LeagueRestrictionException {
		if (name == null || name.equals(""))
			throw new DataException("Required data: league name");
		verifyLeagueNameExistence(name);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean verifyLeagueExistence(Integer leagueId){
		Query query = em.createQuery("Select l From League l Where l.id = :id");
		query.setParameter("id",leagueId);
		List<League> league = query.getResultList(); 
		if (league.isEmpty())
			return false;
		return true;
	}
	
	/*
	 * Verify if name of the league existence
	 * @param name The name of the league
	 * @throws LeagueRestrictionException
	 */
	@SuppressWarnings("unchecked")
	private void verifyLeagueNameExistence(String name) throws LeagueRestrictionException {
		Query query = em.createQuery("Select l.name From League l Where l.name = :name");
		query.setParameter("name", name);
		List<String> leagues = query.getResultList();
		if (!leagues.isEmpty()){
			throw new LeagueRestrictionException("This league already exists");
		}
	}

	/*
	 * Verify if user existence
	 * @param userId The ID of the user
	 * @throws UserNotFoundException
	 */
	private void verifyUserExistence(Integer userId) throws UserNotFoundException{
		if (!userService.isAnExistentUser(userId))
			throw new UserNotFoundException("Cannot find user");
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<League> getAllLeagues() {
		Query query = em.createQuery("Select l From League l");
		List<League> allLeagues = query.getResultList();
		return allLeagues;
	}

}
